﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace PuyoNeuron
{
    public class Panel : IUIElement
    {
        internal Vector2 mSize;
        Vector2 mTopLeft;
        string mTitle;
        bool mSizeTainted;
        bool mDispose;
        string mCode;
        string mConstructor;

        static Texture2D sPanelTexture;
        static Texture2D sNubTexture;
        static Texture2D sDotTexture;
        static SpriteFont sTitleFont;
        static SpriteFont sLabelFont;

        internal static Nub sNewWireStart;

        internal List<Nub> mEmitters;
        List<Nub> mReceivers;
        List<Nub> mTriggers;
        List<Nub> mFires;

        internal Vector2? mDraggedBy;

        #region getters and setters

        public string Title
        {
            get { return mTitle; }
            set { mTitle = value; }
        }

        public Vector2 TopLeft
        {
            get { return mTopLeft; }
            set
            {
                mTopLeft = value;

                foreach (Nub lNub in mEmitters)
                    lNub.Tainted = true;

                foreach (Nub lNub in mReceivers)
                    lNub.Tainted = true;

                foreach (Nub lNub in mTriggers)
                    lNub.Tainted = true;

                foreach (Nub lNub in mFires)
                    lNub.Tainted = true;
            }
        }

        public virtual Vector2 Size
        {
            get { return mSize; }
            set { mSize = new Vector2((value.X <= 40) ? 40 : (int)value.X, (value.Y <= 60) ? 60 : (int)value.Y); }
        }

        public bool Dispose
        {
            get { return mDispose; }
            set { mDispose = value; }
        }

        // occasionally overridden by Constant
        public virtual string Code
        {
            get { return mCode; }
            set { mCode = value; }
        }

        // occasionally overridden by Constant
        public virtual string Constructor
        {
            get { return mConstructor; }
            set { mConstructor = value; }
        }

        public List<Nub> Nubs
        {
            get { return mEmitters.Union(mReceivers.Union(mFires.Union(mTriggers))).ToList<Nub>(); }
        }

        static public Texture2D PanelTexture
        {
            get { return sPanelTexture; }
            set { sPanelTexture = value; }
        }

        static public Texture2D NubTexture
        {
            get { return sNubTexture; }
            set { sNubTexture = value; }
        }

        static public Texture2D DotTexture
        {
            get { return sDotTexture; }
            set { sDotTexture = value; }
        }

        static public SpriteFont TitleFont
        {
            get { return sTitleFont; }
            set { sTitleFont = value; }
        }

        static public SpriteFont LabelFont
        {
            get { return sLabelFont; }
            set { sLabelFont = value; }
        }

        #endregion

        public Panel(string pFilename, Vector2 pTopLeft)
        {
            mEmitters = new List<Nub>();
            mReceivers = new List<Nub>();
            mTriggers = new List<Nub>();
            mFires = new List<Nub>();

            StreamReader luaFile = new StreamReader(new FileStream(pFilename, FileMode.Open));

            string lLine;
            while ((lLine = luaFile.ReadLine() ?? "").StartsWith("-- :"))
            {
                if (lLine.StartsWith("-- :build"))
                    mConstructor = lLine.Split('=')[1].Trim();

                if (lLine.StartsWith("-- :name"))
                    mTitle = lLine.Split('=')[1].Trim() + "_" + GetHashCode().ToString("x").ToLower();

                if (lLine.StartsWith("-- :receive"))
                    mReceivers.Add(new Nub(this, lLine.Split('=')[1].Trim()));

                if (lLine.StartsWith("-- :emit"))
                    mEmitters.Add(new Nub(this, lLine.Split('=')[1].Trim()));

                if (lLine.StartsWith("-- :fire"))
                    mFires.Add(new Nub(this, lLine.Split('=')[1].Trim()));

                if (lLine.StartsWith("-- :trigger"))
                    mTriggers.Add(new Nub(this, lLine.Split('=')[1].Trim()));
            }

            mCode = luaFile.ReadToEnd();

            luaFile.BaseStream.Close();

            TopLeft = pTopLeft;
            mSizeTainted = true;

            Snap(20);
        }

        internal void Update()
        {
            if (mDraggedBy.HasValue)
                TopLeft = Camera.Instance.ToWorld(new Vector2(Mouse.GetState().X, Mouse.GetState().Y)) - mDraggedBy.Value;
        }

        internal virtual void Draw(SpriteBatch pSpriteBatch)
        {
            #region resize

            if (mSizeTainted)
            {
                int lMaxEmitter = 0;
                int lMaxReceiver = 0;

                foreach (Nub lNub in mEmitters)
                    if (LabelFont.MeasureString(lNub.Name).X > lMaxEmitter)
                        lMaxEmitter = (int)LabelFont.MeasureString(lNub.Name).X;

                foreach (Nub lNub in mReceivers)
                    if (LabelFont.MeasureString(lNub.Name).X > lMaxReceiver)
                        lMaxReceiver = (int)LabelFont.MeasureString(lNub.Name).X;

                int lWidth = lMaxEmitter + lMaxReceiver + 32;

                if (TitleFont.MeasureString(mTitle).X + 16 > lWidth)
                    lWidth = (int)TitleFont.MeasureString(mTitle).X + 16;

                int lHeight = 40 + (mEmitters.Count + mFires.Count > mReceivers.Count + mTriggers.Count
                                        ? (mEmitters.Count + mFires.Count) * 20
                                        : (mReceivers.Count + mTriggers.Count) * 20);

                // standard grid size is 20 pixels so keep boxes rounded to that...
                lWidth = lWidth / 20 * 20 + 20;

                Size = new Vector2(lWidth, lHeight);
            }

            #endregion

            #region draw box

            // top left
            pSpriteBatch.Draw(sPanelTexture,
                              Camera.Instance.ToScreen(TopLeft + new Vector2(0, 0)),
                              new Rectangle(0, 0, 16, 32),
                              Color.White,
                              0,
                              Vector2.Zero,
                              new Vector2(1, 1) * Camera.Instance.Zoom,
                              SpriteEffects.None,
                              0f);

            // top edge
            pSpriteBatch.Draw(sPanelTexture,
                              Camera.Instance.ToScreen(TopLeft + new Vector2(16, 0)),
                              new Rectangle(16, 0, 32, 32),
                              Color.White,
                              0,
                              Vector2.Zero,
                              new Vector2(Size.X / 32f - 1, 1) * Camera.Instance.Zoom,
                              SpriteEffects.None,
                              0f);

            // top right
            pSpriteBatch.Draw(sPanelTexture,
                              Camera.Instance.ToScreen(TopLeft + new Vector2(Size.X - 16, 0)),
                              new Rectangle(48, 0, 16, 32),
                              Color.White,
                              0,
                              Vector2.Zero,
                              new Vector2(1, 1) * Camera.Instance.Zoom,
                              SpriteEffects.None,
                              0f);

            // left edge
            pSpriteBatch.Draw(sPanelTexture,
                              Camera.Instance.ToScreen(TopLeft + new Vector2(0, 32)),
                              new Rectangle(0, 32, 16, 16),
                              Color.White,
                              0,
                              Vector2.Zero,
                              new Vector2(1, Size.Y / 16f - 3) * Camera.Instance.Zoom,
                              SpriteEffects.None,
                              0f);

            // middle
            pSpriteBatch.Draw(sPanelTexture,
                              Camera.Instance.ToScreen(TopLeft + new Vector2(16, 32)),
                              new Rectangle(16, 32, 32, 16),
                              Color.White,
                              0,
                              Vector2.Zero,
                              new Vector2(Size.X / 32f - 1, Size.Y / 16f - 3) * Camera.Instance.Zoom,
                              SpriteEffects.None,
                              0f);

            // right edge
            pSpriteBatch.Draw(sPanelTexture,
                              Camera.Instance.ToScreen(TopLeft + new Vector2(Size.X - 16, 32)),
                              new Rectangle(48, 32, 16, 16),
                              Color.White,
                              0,
                              Vector2.Zero,
                              new Vector2(1, Size.Y / 16f - 3) * Camera.Instance.Zoom,
                              SpriteEffects.None,
                              0f);

            // bottom left
            pSpriteBatch.Draw(sPanelTexture,
                              Camera.Instance.ToScreen(TopLeft + new Vector2(0, Size.Y - 16)),
                              new Rectangle(0, 48, 16, 16),
                              Color.White,
                              0,
                              Vector2.Zero,
                              new Vector2(1, 1) * Camera.Instance.Zoom,
                              SpriteEffects.None,
                              0f);

            // bottom edge
            pSpriteBatch.Draw(sPanelTexture,
                              Camera.Instance.ToScreen(TopLeft + new Vector2(16, Size.Y - 16)),
                              new Rectangle(16, 48, 32, 16),
                              Color.White,
                              0,
                              Vector2.Zero,
                              new Vector2(Size.X / 32f - 1, 1) * Camera.Instance.Zoom,
                              SpriteEffects.None,
                              0f);

            // bottom left
            pSpriteBatch.Draw(sPanelTexture,
                              Camera.Instance.ToScreen(TopLeft + new Vector2(Size.X - 16, Size.Y - 16)),
                              new Rectangle(48, 48, 16, 16),
                              Color.White,
                              0,
                              Vector2.Zero,
                              new Vector2(1, 1) * Camera.Instance.Zoom,
                              SpriteEffects.None,
                              0f);

            #endregion

            #region draw wires

            Nub lHilight = null;
            foreach (Nub lNubA in mEmitters)
            {
                foreach (Nub lNubB in lNubA.Connections)
                {
                    if ((Camera.Instance.ToWorld(new Vector2(Mouse.GetState().X, Mouse.GetState().Y)) - lNubA.Center).Length() < 9)
                        lHilight = lNubA;

                    DrawWire(pSpriteBatch, Camera.Instance.ToScreen(lNubA.Center), Camera.Instance.ToScreen(lNubB.Center), Color.Black);
                    pSpriteBatch.Draw(NubTexture,
                                      Camera.Instance.ToScreen(lNubB.Center - new Vector2(7, 7)),
                                      new Rectangle(0, 0, 14, 14),
                                      Color.White,
                                      0f,
                                      Vector2.Zero,
                                      Camera.Instance.Zoom,
                                      SpriteEffects.None,
                                      0f);
                }
            }

            foreach (Nub lNubA in mFires)
            {
                foreach (Nub lNubB in lNubA.Connections)
                {
                    if ((Camera.Instance.ToWorld(new Vector2(Mouse.GetState().X, Mouse.GetState().Y)) - lNubA.Center).Length() < 9)
                        lHilight = lNubA;

                    DrawWire(pSpriteBatch, Camera.Instance.ToScreen(lNubA.Center), Camera.Instance.ToScreen(lNubB.Center), Color.Black);
                    pSpriteBatch.Draw(NubTexture,
                                      Camera.Instance.ToScreen(lNubB.Center - new Vector2(7, 7)),
                                      new Rectangle(lNubB.Type == Nub.NubType.Receiver ? 0 : 28, 0, 14, 14),
                                      Color.White,
                                      0f,
                                      Vector2.Zero,
                                      Camera.Instance.Zoom,
                                      SpriteEffects.None,
                                      0f);
                }
            }
            
            // draw hilighted wire on top of other wires
            // only draws on top of wires inside this box... but thats not a super huge issue
            if (lHilight != null)
            {
                foreach (Nub lNubB in lHilight.Connections)
                {
                    DrawWire(pSpriteBatch, Camera.Instance.ToScreen(lHilight.Center), Camera.Instance.ToScreen(lNubB.Center), Color.Yellow);
                    pSpriteBatch.Draw(NubTexture,
                                      Camera.Instance.ToScreen(lNubB.Center - new Vector2(7, 7)),
                                      new Rectangle(lNubB.Type == Nub.NubType.Receiver ? 0 : 28, 0, 14, 14),
                                      Color.White,
                                      0f,
                                      Vector2.Zero,
                                      Camera.Instance.Zoom,
                                      SpriteEffects.None,
                                      0f);
                }
            }

            // draw a wire to the cursor
            if (sNewWireStart != null)
                if (sNewWireStart.Type == Nub.NubType.Emitter || sNewWireStart.Type == Nub.NubType.Fire)
                    DrawWire(pSpriteBatch, Camera.Instance.ToScreen(sNewWireStart.Center), new Vector2(Mouse.GetState().X, Mouse.GetState().Y), Color.Yellow);
                else
                    DrawWire(pSpriteBatch, new Vector2(Mouse.GetState().X, Mouse.GetState().Y), Camera.Instance.ToScreen(sNewWireStart.Center), Color.Yellow);

            #endregion

            #region draw nubs
            foreach (Nub lNub in mReceivers)
            {
                pSpriteBatch.Draw(NubTexture, Camera.Instance.ToScreen(lNub.Center - new Vector2(7, 7)), new Rectangle(0, 0, 14, 14), Color.White,
                                      0f,
                                      Vector2.Zero,
                                      Camera.Instance.Zoom,
                                      SpriteEffects.None,
                                      0f);
                pSpriteBatch.DrawString(LabelFont,
                                        lNub.Name,
                                        Camera.Instance.ToScreen(lNub.Center + new Vector2(10, -10)),
                                        Color.Black,
                                        0f,
                                        Vector2.Zero,
                                        Camera.Instance.Zoom,
                                        SpriteEffects.None,
                                        0f);
            }

            foreach (Nub lNub in mTriggers)
            {
                pSpriteBatch.Draw(NubTexture, Camera.Instance.ToScreen(lNub.Center - new Vector2(7, 7)), new Rectangle(28, 0, 14, 14), Color.White,
                                      0f,
                                      Vector2.Zero,
                                      Camera.Instance.Zoom,
                                      SpriteEffects.None,
                                      0f);
                pSpriteBatch.DrawString(LabelFont,
                                        lNub.Name,
                                        Camera.Instance.ToScreen(lNub.Center + new Vector2(10, -10)),
                                        Color.Black,
                                        0f,
                                        Vector2.Zero,
                                        Camera.Instance.Zoom,
                                        SpriteEffects.None,
                                        0f);
            }

            foreach (Nub lNub in mEmitters)
            {
                pSpriteBatch.Draw(NubTexture, Camera.Instance.ToScreen(lNub.Center - new Vector2(7, 7)), new Rectangle(14, 0, 14, 14), Color.White,
                                      0f,
                                      Vector2.Zero,
                                      Camera.Instance.Zoom,
                                      SpriteEffects.None,
                                      0f);
                pSpriteBatch.DrawString(LabelFont,
                                        lNub.Name,
                                        Camera.Instance.ToScreen(lNub.Center + new Vector2(-10 - LabelFont.MeasureString(lNub.Name).X, -10)),
                                        Color.Black,
                                        0f,
                                        Vector2.Zero,
                                        Camera.Instance.Zoom,
                                        SpriteEffects.None,
                                        0f);
            }

            foreach (Nub lNub in mFires)
            {
                pSpriteBatch.Draw(NubTexture, Camera.Instance.ToScreen(lNub.Center - new Vector2(7, 7)), new Rectangle(42, 0, 14, 14), Color.White,
                                      0f,
                                      Vector2.Zero,
                                      Camera.Instance.Zoom,
                                      SpriteEffects.None,
                                      0f);
                pSpriteBatch.DrawString(LabelFont,
                                        lNub.Name,
                                        Camera.Instance.ToScreen(lNub.Center + new Vector2(-10 - LabelFont.MeasureString(lNub.Name).X, -10)),
                                        Color.Black,
                                        0f,
                                        Vector2.Zero,
                                        Camera.Instance.Zoom,
                                        SpriteEffects.None,
                                        0f);
            }
            #endregion

            pSpriteBatch.DrawString(TitleFont,
                                    Title,
                                    Camera.Instance.ToScreen(TopLeft + new Vector2(8, 0)),
                                    Color.White,
                                    0f,
                                    Vector2.Zero,
                                    Camera.Instance.Zoom,
                                    SpriteEffects.None,
                                    0f);
        }

        internal void DrawWire(SpriteBatch pSpriteBatch, Vector2 pA, Vector2 pB, Color pColor)
        {
            Vector2 lV = Vector2.Zero;

            for (float i = 0; i < 1; i += .002f)
            {
                Vector2 lA = pA;
                Vector2 lC = pB;
                Vector2 lB = new Vector2((pA - pB).Length(), 0);
                Vector2 lD = new Vector2((pA - pB).Length(), 0);
                Vector2.Hermite(ref lA, ref lB, ref lC, ref lD, i, out lV);

                pSpriteBatch.Draw(DotTexture, lV - new Vector2(3, 3), new Rectangle(0, 0, 12, 12), pColor,
                                      0f,
                                      Vector2.Zero,
                                      Camera.Instance.Zoom,
                                      SpriteEffects.None,
                                      0f);
            }
        }

        internal void Snap(float pGrid)
        {
            TopLeft = new Vector2((int)Math.Round(TopLeft.X / pGrid) * pGrid, (int)Math.Round(TopLeft.Y / pGrid) * pGrid);
        }

        public virtual ContextMenu GetContextMenuFor(Vector2 pLocation)
        {
            ContextMenu lRet = new ContextMenu(mTopLeft + pLocation);
            bool lNubClicked = false;
            foreach (Nub lNub in mEmitters.Union(mReceivers.Union(mFires.Union(mTriggers))))
            {
                Nub lNubAnchor = lNub;
                if ((pLocation + mTopLeft - lNub.Center).Length() < 9)
                {
                    lRet.Items.Add(new ContextMenu.ContextItem("break all",
                        delegate(Vector2 pPos) {
                            lNubAnchor.BreakAll();
                        }));


                    foreach (Nub lSingleNub in lNub.Connections.OrderBy(pA => pA.Center.Y))
                    {
                        Nub lSingleNubAnchor = lSingleNub;
                        lRet.Items.Add(new ContextMenu.ContextItem("break " + lSingleNub.Parent.Title,
                            delegate(Vector2 pPos) {
                                lSingleNubAnchor.BreakConnection(lNubAnchor);
                            }));
                    }

                    lNubClicked = true;
                }
            }

            if (!lNubClicked)
            {
                lRet.Items.Add(new ContextMenu.ContextItem("delete", delegate(Vector2 pPos) { mDispose = true; }));
            }
            return lRet;
        }

        #region events

        public virtual void OnClick(Vector2 pLocation)
        {
            foreach (Nub lNub in Nubs)
            {
                if ((pLocation + TopLeft - lNub.Center).Length() < 9)
                    sNewWireStart = lNub;
            }

            if (sNewWireStart == null)
                mDraggedBy = pLocation;
        }

        public void OnRelease(Vector2 pLocation)
        {
            if (sNewWireStart != null)
            {
                List<Nub> lEndType;
                if (sNewWireStart.Type == Nub.NubType.Emitter)
                    lEndType = mReceivers;
                else if (sNewWireStart.Type == Nub.NubType.Receiver)
                    lEndType = mEmitters;
                else if (sNewWireStart.Type == Nub.NubType.Trigger)
                    lEndType = mFires;
                else
                    lEndType = mTriggers;
                foreach (Nub lNub in lEndType)
                {
                    if ((pLocation + TopLeft - lNub.Center).Length() < 9)
                    {
                        if (!sNewWireStart.Connections.Contains(lNub))
                        {
                            sNewWireStart.Connect(lNub);
                        }
                    }
                }
            }
        }

        public void OnMouseDown(Vector2 pLocation)
        {
        }

        public void OnMouseUp(Vector2 pLocation)
        {
            if (mDraggedBy != null)
                Snap(20);
            mDraggedBy = null;
            sNewWireStart = null;
        }

        public void OnEnter()
        {
        }

        public void OnExit()
        {
        }

        #endregion

        public class Nub
        {
            public Panel Parent { get; set; }
            public List<Nub> Connections { get; set; }
            public string Name { get; set; }
            public bool Tainted { get; set; }

            public enum NubType
            {
                Emitter,
                Receiver,
                Trigger,
                Fire
            } ;
            
            Vector2 mCenter;

            public Nub(Panel pParent, string pName)
            {
                Name = pName;
                Parent = pParent;
                Tainted = true;
                Connections = new List<Nub>();
            }

            public NubType Type
            {
                get
                {
                    if (Parent.mEmitters.Contains(this))
                        return NubType.Emitter;
                    if (Parent.mReceivers.Contains(this))
                        return NubType.Receiver;
                    if (Parent.mTriggers.Contains(this))
                        return NubType.Trigger;
                    return NubType.Fire;
                }
            }

            public virtual Vector2 Center
            {
                get
                {
                    if (Tainted)
                    {
                        if (Parent is Constant)
                            return mCenter = Parent.Size + Parent.TopLeft + new Vector2(-3, -20);

                        int lX, lY;

                        // uwaahhh i'm sorry. >_< its really a lot of turnary and magic numbers... T___T
                        // and it was sneaky of me to hide this in a field and not a method. T___T
                        if (Type == NubType.Emitter)
                        {
                            lY = 40 + Parent.mEmitters.IndexOf(this) * 20
                                 + (Parent.mReceivers.Count + Parent.mTriggers.Count > Parent.mEmitters.Count + Parent.mFires.Count
                                        ? (Parent.mReceivers.Count + Parent.mTriggers.Count - Parent.mEmitters.Count - Parent.mFires.Count) * 10
                                        : 0);
                            lX = (int)Parent.Size.X - 3;
                        }
                        else if (Type == NubType.Receiver)
                        {
                            lY = 40 + Parent.mReceivers.IndexOf(this) * 20
                                 + (Parent.mEmitters.Count + Parent.mFires.Count > Parent.mReceivers.Count + Parent.mTriggers.Count
                                        ? (Parent.mEmitters.Count + Parent.mFires.Count - Parent.mReceivers.Count - Parent.mTriggers.Count) * 10
                                        : 0);
                            lX = 3;
                        }
                        else if (Type == NubType.Fire)
                        {
                            lY = 40 + Parent.mEmitters.Count * 20 + Parent.mFires.IndexOf(this) * 20
                                 + (Parent.mReceivers.Count + Parent.mTriggers.Count > Parent.mEmitters.Count + Parent.mFires.Count
                                        ? (Parent.mReceivers.Count + Parent.mTriggers.Count - Parent.mEmitters.Count - Parent.mFires.Count) * 10
                                        : 0);
                            lX = (int)Parent.Size.X - 3;
                        }
                        else
                        {
                            lY = 40 + Parent.mReceivers.Count * 20 + Parent.mTriggers.IndexOf(this) * 20
                                 + (Parent.mEmitters.Count + Parent.mFires.Count > Parent.mReceivers.Count + Parent.mTriggers.Count
                                        ? (Parent.mEmitters.Count + Parent.mFires.Count - Parent.mReceivers.Count - Parent.mTriggers.Count) * 10
                                        : 0);
                            lX = 3;
                        }

                        mCenter = new Vector2(lX, lY) + Parent.TopLeft;
                        Tainted = false;
                    }

                    return mCenter;
                }
            }

            public void Connect(Nub pNub)
            {
                if (pNub.Type == NubType.Receiver && pNub.Connections.Count > 0)
                    pNub.BreakAll();

                if (Type == NubType.Receiver && Connections.Count > 0)
                    BreakAll();

                CodeModel.Instance.Connect(this, pNub);
                Connections.Add(pNub);
                pNub.Connections.Add(this);
            }

            public void BreakConnection(Nub pNub)
            {
                CodeModel.Instance.Break(this, pNub);
                Connections.Remove(pNub);
                pNub.Connections.Remove(this);
            }

            public void BreakAll()
            {
                foreach (Nub lNub in Connections)
                    CodeModel.Instance.Break(this, lNub);

                foreach (Nub lNub in Connections)
                    lNub.Connections.Remove(this);

                Connections = new List<Nub>();
            }
        }
    }
}